#pragma once

#include "znx_event.h"
#include "comm/znx_buf.h"
#include "comm/znx_log.h"
#include "comm/znx_inet.h"
#include "comm/znx_pool.h"
#include "comm/znx_syscall.h"

typedef enum {
    ZNX_CONN_EPOLL_LT,
    ZNX_CONN_EPOLL_ET,
} znx_conn_flag_t;

typedef struct znx_tcp_listener_s znx_tcp_listener_t;

typedef struct znx_conn_s       znx_conn_t;
typedef struct znx_ssl_conn_s   znx_ssl_conn_t;

typedef void (*znx_conn_free_handle_pt)(znx_conn_t *conn);
typedef void (*znx_conn_handle_pt)(znx_conn_t *conn);


struct znx_conn_s {
    void                    *data;
    int                     fd;                 // sockfd
    znx_event_t             read;
    znx_event_t             write;
    znx_conn_flag_t         flag;
    znx_epoll_event_t       ee;

    znx_pool_t              *pool;

    znx_ssl_conn_t          *ssl;

    znx_address_t           *local_address;
    znx_str_t               local_address_text;
    znx_address_t           *peer_address;
    znx_str_t               peer_address_text;

    znx_tcp_listener_t      *ls;

    int64_t                 recv_bytes;
    int64_t                 sent_bytes;
    int64_t                 requests;

    unsigned                start:1;            // start io
    unsigned                shared:1;
};

static inline void
znx_conn_init(znx_conn_t *conn, znx_conn_flag_t flag)
{
    // it's a read event
    conn->read.data = conn;
    conn->read.write = 0;

    // it's a write event
    conn->write.data = conn;
    conn->write.write = 1;

    // epoll event
    conn->flag = flag;
    conn->ee.data.ptr = conn;
    conn->fd = INVALID_FD;

    conn->pool = znx_pool_create();
}


static inline znx_syscall_result_t
znx_conn_read(znx_conn_t *conn, void *buff, size_t count)
{
    znx_syscall_result_t res;
    res = znx_syscall_read(conn->fd, buff, count);

    if (res.res > 0) {
        conn->recv_bytes += res.res;
        return res;
    }

    conn->read.ready = 0;

    if (res.res == 0) {
        conn->read.eof = 1;
        return res;
    }

    if (res.eno != EAGAIN) {
        conn->read.error = 1;
    }

    return res;
}


static inline znx_syscall_result_t
znx_conn_readv(znx_conn_t *conn, znx_iovec_t *vector, int count)
{
    znx_syscall_result_t res;
    res = znx_syscall_readv(conn->fd, vector, count);

    if (res.res > 0) {
        conn->recv_bytes += res.res;
        return res;
    }

    conn->read.ready = 0;

    if (res.res == 0) {
        conn->read.eof = 1;
        return res;
    }

    if (res.eno != EAGAIN) {
        conn->read.error = 1;
    }

    return res;
}


static inline znx_syscall_result_t
znx_conn_write(znx_conn_t *conn, void *buff, size_t count)
{
    znx_syscall_result_t res;
    res = znx_syscall_write(conn->fd, buff, count);

    if (res.res > 0) {
        conn->sent_bytes += res.res;
        return res;
    }

    conn->write.ready = 0;

    if (res.res == 0) {
        conn->write.eof = 1;
        return res;
    }

    if (res.eno != EAGAIN) {
        conn->write.error = 1;
    }

    return res;
}


static inline znx_syscall_result_t
znx_conn_writev(znx_conn_t *conn, znx_iovec_t *vector, int count)
{
    znx_syscall_result_t res;
    res = znx_syscall_writev(conn->fd, vector, count);

    if (res.res > 0) {
        conn->sent_bytes += res.res;
        return res;
    }

    conn->write.ready = 0;

    if (res.res == 0) {
        conn->write.eof = 1;
        return res;
    }

    if (res.eno != EAGAIN) {
        conn->write.error = 1;
    }

    return res;
}


static inline znx_syscall_result_t
znx_conn_udp_recv(znx_conn_t *conn, void *buff, size_t count)
{
    znx_syscall_result_t res;
    res = znx_syscall_recv(conn->fd, buff, count, 0);

    if (res.res > 0) {
        conn->recv_bytes += res.res;
        return res;
    }

    conn->read.ready = 0;

    if (res.res == 0) {
        conn->read.eof = 1;
        return res;
    }

    if (res.eno != EAGAIN) {
        conn->read.error = 1;
    }

    return res;
}


static inline znx_syscall_result_t
znx_conn_udp_send(znx_conn_t *conn, void *buff, size_t count)
{
    znx_syscall_result_t res;
    res = znx_syscall_send(conn->fd, buff, count, 0);

    if (res.res > 0) {
        conn->sent_bytes += res.res;
        return res;
    }

    conn->write.ready = 0;

    if (res.res == 0) {
        conn->write.eof = 1;
        return res;
    }

    if (res.eno != EAGAIN) {
        conn->write.eof = 1;
    }

    return res;
}


static inline znx_bool_t
znx_conn_local_address_init(znx_conn_t *conn)
{
    znx_address_t           address;
    socklen_t               address_len;
    znx_syscall_result_t    sres;
    int                     res;

    address_len = sizeof(znx_address_t);
    sres = znx_syscall_getsockname(conn->fd,
            (znx_sockaddr_t *) &address, &address_len);
    if (sres.eno != 0) {
        return ZNX_FALSE;
    }

    conn->local_address = znx_pool_calloc(conn->pool, sizeof(znx_address_t));
    znx_memcpy(conn->local_address, &address, address_len);

    conn->local_address_text.data = znx_pool_calloc(conn->pool, ZNX_SOCKADDR_SIZE);
    res = znx_address_to_str(&address,
        conn->local_address_text.data, ZNX_SOCKADDR_SIZE);
    // res == 0 mean failed, but we return ZNX_TRUE.
    conn->local_address_text.len = (size_t)res;

    return ZNX_TRUE;
}


static inline znx_address_t *
znx_conn_local_address(znx_conn_t *conn)
{
    if (conn->local_address != NULL) {
        return conn->local_address;
    }

    if (conn->fd == INVALID_FD) {
        return NULL;
    }

    if (!znx_conn_local_address_init(conn)) {
        return NULL;
    }

    return conn->local_address;
}


static inline znx_str_t *
znx_conn_local_address_text(znx_conn_t *conn)
{
    if (conn->local_address != NULL) {
        return &conn->local_address_text;
    }

    if (conn->fd == INVALID_FD) {
        return NULL;
    }

    if (!znx_conn_local_address_init(conn)) {
        return NULL;
    }

    return &conn->local_address_text;
}


static inline znx_bool_t
znx_conn_peer_address_init(znx_conn_t *conn)
{
    znx_address_t           address;
    socklen_t               address_len;
    znx_syscall_result_t    sres;
    int                     res;

    address_len = sizeof(znx_address_t);
    sres = znx_syscall_getpeername(conn->fd,
            (znx_sockaddr_t *) &address, &address_len);
    if (sres.eno != 0) {
        return ZNX_FALSE;
    }

    conn->peer_address = znx_pool_calloc(conn->pool, sizeof(znx_address_t));
    znx_memcpy(conn->peer_address, &address, address_len);

    conn->peer_address_text.data = znx_pool_calloc(conn->pool, ZNX_SOCKADDR_SIZE);
    res = znx_address_to_str(&address,
        conn->peer_address_text.data, ZNX_SOCKADDR_SIZE);
    // res == 0 mean failed, but we return ZNX_TRUE.
    conn->peer_address_text.len = (size_t)res;

    return ZNX_TRUE;
}


static inline znx_address_t *
znx_conn_peer_address(znx_conn_t *conn)
{
    if (conn->peer_address != NULL) {
        return conn->peer_address;
    }

    if (conn->fd == INVALID_FD) {
        return NULL;
    }

    if (!znx_conn_peer_address_init(conn)) {
        return NULL;
    }

    return conn->peer_address;
}


static inline znx_str_t *
znx_conn_peer_address_text(znx_conn_t *conn)
{
    if (conn->peer_address != NULL) {
        return &conn->peer_address_text;
    }

    if (conn->fd == INVALID_FD) {
        return NULL;
    }

    if (!znx_conn_peer_address_init(conn)) {
        return NULL;
    }

    return &conn->peer_address_text;
}


static inline void
znx_conn_close(znx_conn_t *conn)
{
    if (conn->ssl) {
        // TODO ssl conn close...
    }

    if (conn->pool) {
        znx_pool_destroy(conn->pool);
    }

    if (conn->fd == INVALID_FD) {
        return;
    }

    if (conn->shared) {
        conn->fd = INVALID_FD;
        return;
    }

    znx_syscall_close(conn->fd);
    conn->fd = INVALID_FD;
}


static inline void
znx_conn_close_handle(void *data)
{
    znx_conn_t *conn = (znx_conn_t *)data;
    znx_conn_close(conn);
}


static inline void
znx_conn_close_and_free(void *data)
{
    znx_conn_t *conn = (znx_conn_t *)data;
    znx_conn_close(conn);
    znx_thread_free(conn);
}

